package com.googlecode.allinclusive.fw.core.data.mapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.xml.DefaultDocumentLoader;
import org.springframework.beans.factory.xml.DocumentLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.xml.SimpleSaxErrorHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.googlecode.allinclusive.fw.core.data.input.excel.config.ExcelColumn;
import com.googlecode.allinclusive.fw.core.data.input.excel.config.ExcelConfig;


/**
 * Excel数据映射文件解析器 
 */
public class XMLExcelMappingParser implements MappingParser {

	private static final Log log = LogFactory.getLog(XMLExcelMappingParser.class);
	
	private static final String EXCEL_MAPPINGS = "excel-mappings";
	private static final String EXCEL_MAPPING = "excel-mapping";
	
	private boolean validate = false;
	
	/**
	 * 解析数据映射文件
	 * @param mappingLocations 数据映射文件
	 * @return 解析结果
	 */
	@Override
	public Object parse(String[] mappingLocations) {
		
		Map<String, ExcelConfig> mappingConfigs = new HashMap<String, ExcelConfig>();
		try {
			// mappingResources
			List<Resource> mappingResources = new ArrayList<Resource>();
			ResourcePatternResolver resLoader = new PathMatchingResourcePatternResolver();
			for (String mappingLocation : mappingLocations) {
				mappingResources.addAll(Arrays.asList(resLoader.getResources(mappingLocation)));
			}
			// validate
			if (validate) {
				validate(mappingResources);
			}
			
			// parse
			DocumentLoader docLoader = new DefaultDocumentLoader();
			for (Resource mappingResource : mappingResources) {
				Document doc = docLoader.loadDocument(new InputSource(mappingResource.getInputStream()), null, new SimpleSaxErrorHandler(log), 0, false);
				
				Element root = doc.getDocumentElement();
				// root name = excel-mappings?
				if (!EXCEL_MAPPINGS.equals(root.getNodeName())) {
					throw new RuntimeException("root name error.");
				}
				
				NodeList nodeList = root.getElementsByTagName(EXCEL_MAPPING);
				for (int i = 0; i < nodeList.getLength(); i++) {
					Element excelMapping = (Element) nodeList.item(i);
					ExcelConfig excelConfig = parseExcelMapping(excelMapping);
					
					mappingConfigs.put(excelConfig.getId(), excelConfig);
				}
			}
			
			return mappingConfigs;
		} catch (IOException ioe) {
			throw new RuntimeException(ioe.getMessage(), ioe);
		} catch (SAXException saxe) {
			throw new RuntimeException(saxe.getMessage(), saxe);
		} catch (Exception ex) {
			throw new RuntimeException(ex.getMessage(), ex);
		}
	}
	
	private ExcelConfig parseExcelMapping(Element excelMapping) {
		ExcelConfig excelConfig = new ExcelConfig();

		// id
		excelConfig.setId(excelMapping.getAttribute("id"));
		// sheet
		if (excelMapping.hasAttribute("sheet")) {
			excelConfig.setSheet(Integer.parseInt(excelMapping.getAttribute("sheet")));
		}
		// start row
		if (excelMapping.hasAttribute("start-row")) {
			excelConfig.setStartRow(Integer.parseInt(excelMapping.getAttribute("start-row")));
		}
		// start col
		if (excelMapping.hasAttribute("start-col")) {
			excelConfig.setStartCol(Integer.parseInt(excelMapping.getAttribute("start-col")));
		}
		// columns
		NodeList columns = excelMapping.getElementsByTagName("column");
		if (columns == null || columns.getLength() <= 0) {
			return excelConfig;
		}
		
		for (int i = 0; i < columns.getLength(); i++) {
			Element column = (Element) columns.item(i);
			
			ExcelColumn excelColumn = new ExcelColumn();
			// property
			excelColumn.setProperty(column.getAttribute("property"));
			
			excelConfig.addColumn(excelColumn);
		}
		
		return excelConfig;
	}

	private void validate(List<Resource> mappingResources) throws SAXException, IOException {
		SchemaFactory sf = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
		Source source = new StreamSource(getClass().getResourceAsStream("excel-mappings.xsd"));
		Schema schema = sf.newSchema(source);
		Validator validator = schema.newValidator();
		for (Resource mappingResource : mappingResources) {
			validator.validate(new StreamSource(mappingResource.getInputStream()));
		}
	}
}
